﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Xceed.Silverlight.Data;
using Xceed.Silverlight.ListBox.PositioningFunction;
using System.Collections.Generic;
using System.Diagnostics;
using Xceed.Silverlight.Utils;

namespace Xceed.Silverlight.ListBox
{
  public partial class PanelBase
  {
    internal class PanelLayoutManager
    {
      #region Constructor

      public PanelLayoutManager( PanelBase parentPanel )
      {
        m_panel = parentPanel;
      }

      #endregion

      #region ParentListBox Property

      public ListBox ParentListBox
      {
        get
        {
          return m_panel.ParentListBox;
        }
      }

      #endregion


      #region ContainerDescriptions Property

      internal ContainerDescriptionList ContainerDescriptions
      {
        get
        {
          return m_containerDescriptions;
        }
      }

      #endregion

      #region DataPathToContainerMapping

      internal Dictionary<DataPath, ListBoxContainer> DataPathToContainerMapping
      {
        get
        {
          return m_dataPathToContainerMapping;
        }
      }

      #endregion

      #region StickyHeader Property

      internal ListBoxContainer StickyHeader
      {
        get
        {
          return m_stickyHeader;
        }
      }

      #endregion

      #region IsLayoutInhibited Internal Property

      internal bool IsLayoutInhibited
      {
        get
        {
          return ( m_inhibitLayoutCount > 0 );
        }
      }

      #endregion


      #region UpdateViewportContent Methods

      private void RecycleContainersBeforeUpdatingViewportContent()
      {
        Dictionary<DataPath, ListBoxContainer> previousDataPathToContainerMapping =
            new Dictionary<DataPath, ListBoxContainer>( m_dataPathToContainerMapping );

        foreach( KeyValuePair<DataPath, ListBoxContainer> keyValuePair in previousDataPathToContainerMapping )
        {
          DataPath dataPath = keyValuePair.Key;
          ListBoxContainer container = keyValuePair.Value;

          if( m_containerDescriptions[ dataPath ] == null )
          {
            this.RemoveDataPathToContainerMapping( dataPath );

            if( !m_isUpdatingViewportForTransition )
            {
              // Even though we do not explicitly want to defer container recycling
              // the container will be put in the waiting list if it is not elligible
              // for recycling.
              m_panel.RecycleContainerAndLayoutOutOfView( container );
            }
            else
            {
              // Transition under way.
              // Add container to the recycling waiting list for future recycling.
              m_panel.AddContainerToRecyclingWaitingList( container );

              if( !FocusHelper.HasFocus( container ) )
              {
                container.UpdateLayoutTransform();
              }
            }
          }
        }
      }

      private ContainerDescriptionList PrepareViewportContent( Size viewport )
      {
        ListBox listBox = this.ParentListBox;
        int numberOfMoveInDataSource = 0;
        ContainerDescriptionList containerDescriptions = new ContainerDescriptionList();
        int totalNumberOfItemsLayouted = 0;

        // Data management
        DataSourceWrapper dataSourceWrapper = listBox.DataSourceWrapper;
        DataSourceEnumeratorWrapper dataSourceEnumerator = listBox.DataSourceEnumeratorWrapper;

        if( dataSourceEnumerator.IsCachingEnumerator )
        {
          //* 2 is a safe value to ensure cache will keep elements in current layout.
          dataSourceEnumerator.SetCacheSize( Int32.MaxValue );
        }

        if( dataSourceEnumerator == null )
          return null;

        // Do not use the Enumerator if there are no items
        // in the DataSource.
        if( dataSourceEnumerator.ItemCount == 0 )
          return containerDescriptions;

        // TopBottom Layout Pass
        numberOfMoveInDataSource = this.LayoutFromCurrentToViewportEnd( viewport, containerDescriptions );
        totalNumberOfItemsLayouted += numberOfMoveInDataSource;

        // Roll back the DataEnumerator
        if( numberOfMoveInDataSource > 0 )
          m_panel.EnumeratorHelper.Move( -( numberOfMoveInDataSource ) );

        // BottomTop Layout Pass
        numberOfMoveInDataSource = this.LayoutFromCurrentToViewportStart( viewport, containerDescriptions );

        totalNumberOfItemsLayouted += numberOfMoveInDataSource;


        // Start roll backing the DataEnumerator.
        int offsetToGetOnFirstLayoutedItem = numberOfMoveInDataSource;
        if( offsetToGetOnFirstLayoutedItem > 0 )
        {
          int offsetToGetBeforeFirstLayoutedItem = offsetToGetOnFirstLayoutedItem - 1;

          if( m_panel.EnumeratorHelper.Move( offsetToGetBeforeFirstLayoutedItem ) )
          {
            offsetToGetOnFirstLayoutedItem -= offsetToGetBeforeFirstLayoutedItem;
          }
          else
          {
            m_panel.EnumeratorHelper.Move( 1 );
            offsetToGetOnFirstLayoutedItem = 0;
          }
        }

        if( dataSourceEnumerator.IsCachingEnumerator )
        {
          //* 4 is a safe value to ensure cache will keep elements in current layout.
          dataSourceEnumerator.SetCacheSize( totalNumberOfItemsLayouted * 4 + 1 );

        }

        // Complete the roll back of the DataEnumerator.
        if( offsetToGetOnFirstLayoutedItem > 0 )
        {
          m_panel.EnumeratorHelper.Move( offsetToGetOnFirstLayoutedItem );
        }

        // Correct the viewport height according to the positioning function.
        m_panel.PositioningFunction.CorrectViewportSize( m_panel.m_itemsInViewport, ref m_panel.m_viewport );

        ListBoxContainerConfigurationBase groupConfiguration = listBox.GetContainerFactory( ConfigurationType.GroupHeader );

        if( ( m_panel.AllowStickyHeaders ) &&
            ( groupConfiguration.IsSticky ) )
        {
          m_panel.PrepareStickyHeaderDescription( containerDescriptions );
        }

        this.UpdateScrollToTopBottomIndexes( containerDescriptions );

        return containerDescriptions;
      }

      private void UpdateScrollToTopBottomIndexes( ContainerDescriptionList containerDescriptions )
      {
        // Find out how many items are completely out of view and the Item count (without GroupHeaders)
        int outOfViewAtTopItemCount = 0;
        int outOfViewAtTopNonGroupHeaderItemCount = 0;
        int outOfViewAtBottomItemCount = 0;
        int outOfViewAtBottomNonGroupHeaderItemCount = 0;
        int totalNonGroupHeaderItemCount = 0;
        bool foundFirstVisibleItem = false;

        foreach( ContainerDescription containerDescription in containerDescriptions )
        {
          bool isNotGroupHeader = !( containerDescription.Configuration is ListBoxGroupHeaderConfiguration );

          if( containerDescription.ProportionInEffectiveViewport <= 0.0d )
          {
            if( !foundFirstVisibleItem )
            {
              outOfViewAtTopItemCount++;

              if( isNotGroupHeader )
              {
                outOfViewAtTopNonGroupHeaderItemCount++;
              }
            }
            else
            {
              outOfViewAtBottomItemCount++;

              if( isNotGroupHeader )
              {
                outOfViewAtBottomNonGroupHeaderItemCount++;
              }
            }
          }
          else
          {
            foundFirstVisibleItem = true;
          }

          if( isNotGroupHeader )
          {
            totalNonGroupHeaderItemCount++;
          }
        }

        // Find the top and bottom item visible proportions, as all in between will be 100% visible
        double topItemVisibleProportion = ( outOfViewAtTopItemCount < containerDescriptions.Count ) ? containerDescriptions[ outOfViewAtTopItemCount ].ProportionInEffectiveViewport : 0;
        double topItemAdjustedVisibleProportion = topItemVisibleProportion;
        // if description is a group
        if( ( outOfViewAtTopItemCount < containerDescriptions.Count ) && ( containerDescriptions[ outOfViewAtTopItemCount ].Configuration is ListBoxGroupHeaderConfiguration ) )
        {
          topItemAdjustedVisibleProportion = ( 1 - m_panel.GetGroupHeaderProportion() + m_panel.GetGroupHeaderProportion() * topItemVisibleProportion );
        }
        // if previous description is a group
        else if( ( outOfViewAtTopItemCount > 0 ) && ( outOfViewAtTopItemCount <= containerDescriptions.Count ) &&
          ( containerDescriptions[ outOfViewAtTopItemCount - 1 ].Configuration is ListBoxGroupHeaderConfiguration ) )
        {
          topItemAdjustedVisibleProportion = topItemVisibleProportion * ( 1 - m_panel.GetGroupHeaderProportion() );
        }

        double bottomItemVisibleProportion = containerDescriptions[ containerDescriptions.Count - 1 - outOfViewAtBottomItemCount ].ProportionInEffectiveViewport;
        double bottomItemAdjustedVisibleProportion = bottomItemVisibleProportion;
        // if description is a group
        if( ( outOfViewAtBottomItemCount > 0 ) && ( ( containerDescriptions.Count - 1 ) >= outOfViewAtBottomItemCount ) && 
          ( containerDescriptions[ containerDescriptions.Count - 1 - outOfViewAtBottomItemCount ].Configuration is ListBoxGroupHeaderConfiguration ) )
        {
          bottomItemAdjustedVisibleProportion = bottomItemVisibleProportion * m_panel.GetGroupHeaderProportion();
        }
        // if previous description is a group
        else if( ( totalNonGroupHeaderItemCount > 1 ) && ( ( containerDescriptions.Count - 2 ) >= outOfViewAtBottomItemCount ) &&
          ( containerDescriptions[ containerDescriptions.Count - 2 - outOfViewAtBottomItemCount ].Configuration is ListBoxGroupHeaderConfiguration ) )
        {
          bottomItemAdjustedVisibleProportion = bottomItemVisibleProportion * ( 1 - m_panel.GetGroupHeaderProportion() ) + m_panel.GetGroupHeaderProportion();
        }

        // Calculate offset created by the presence of a StickyHeader
        double stickyHeaderOffset = 0.0d;
        if( containerDescriptions.ForcedStickyContainerDescription != null )
        {
          stickyHeaderOffset = m_panel.GetGroupHeaderProportion() / ( 1 - m_panel.GetGroupHeaderProportion() );
        }

        // Determine scroll indexes for all container descriptions
        ContainerDescription currentDescription = null;
        int itemCount = 0;
        bool inhibitStickyHeaderOffsetting = false;
        for( int i = 0; i < containerDescriptions.Count; i++ )
        {
          currentDescription = containerDescriptions[ i ];
          double containerTopItemVisibleProportion = ( i == outOfViewAtTopItemCount ) ? topItemAdjustedVisibleProportion : topItemVisibleProportion;
          double containerBottomItemVisibleProportion = ( i == containerDescriptions.Count - 1 - outOfViewAtBottomItemCount ) ? bottomItemAdjustedVisibleProportion : bottomItemVisibleProportion;

          // Calculate scroll indexes
          currentDescription.ScrollIndexAsTopItem = m_panel.ScrollManager.ActualScrollIndex + ( itemCount - 1 ) + containerTopItemVisibleProportion - outOfViewAtTopNonGroupHeaderItemCount;
          currentDescription.ScrollIndexAsBottomItem = m_panel.ScrollManager.ActualScrollIndex - ( totalNonGroupHeaderItemCount - 1 - itemCount ) + ( 1 - containerBottomItemVisibleProportion ) +
            outOfViewAtBottomNonGroupHeaderItemCount;

          bool isGroupHeader = currentDescription.Configuration is ListBoxGroupHeaderConfiguration;

          // Offset ScrollIndexAsTopItem unless previous item or current description is a group header
          if( (!inhibitStickyHeaderOffsetting) && (!isGroupHeader) )
          {
            currentDescription.ScrollIndexAsTopItem -= stickyHeaderOffset;
          }

          // Inhibit StickyHeader offseting for next item if this a Group Header
          inhibitStickyHeaderOffsetting = isGroupHeader;

          // Increase the itemCount if it's not a GroupHeader
          if( !isGroupHeader )
          {
            itemCount++;
          }
        }
      }

      internal void UpdateViewportContent( Size viewport )
      {
        if( !m_panel.IsInitialized )
        {
          m_panel.Initialize( viewport );
          return;
        }

        if( this.IsLayoutInhibited )
          return;

        ListBox listBox = this.ParentListBox as ListBox;
        bool needRecursiveCall = false;
        bool isScrollManagerActive = m_panel.ScrollManager.IsScrollManagerActive;
        ListBoxContainer currentFocusContainer = m_panel.SelectionAndFocusManager.FindFocusedContainer();

        IScalingFunction scalingFunction = m_panel.GetScalingFunction();

        using( this.DeferUpdateViewport() )
        {
          m_containerDescriptions = this.PrepareViewportContent( viewport );

          if( m_containerDescriptions == null )
            return;

          this.RecycleContainersBeforeUpdatingViewportContent();

          int containerDescriptionCount = m_containerDescriptions.Count;
          int containerCounter = 0;
          for( int i = 0; i < containerDescriptionCount; i++ )
          {
            ContainerDescription containerDescription = m_containerDescriptions[ i ];
            DataPath containerDataPath = containerDescription.DataPath;
            ListBoxContainerConfigurationBase configuration = containerDescription.Configuration;
            ListBoxContainer container = null;

            m_dataPathToContainerMapping.TryGetValue( containerDataPath, out container );

            // Is the focus container contains the dataPath we want to display ?
            if( ( container == null ) &&
              ( currentFocusContainer != null ) )
            {
              // If the current focused container as been recycled
              if( currentFocusContainer.DataPath != null )
              {
                // The current focus container is coming back in view
                if( currentFocusContainer.DataPath.Equals( containerDataPath ) )
                {
                  // The container is the focused container
                  container = currentFocusContainer;

                  // Do not recycle this container
                  m_panel.m_recyclingWaitingList.Remove( container );

                  if( container != m_stickyHeader )
                  {
                    // Add this container to the DataPath to Container mapping
                    this.AddDataPathToContainerMapping( containerDataPath, container );
                  }
                }
              }
            }

            // The container was not the focused container and not found in the current DataPath to 
            // container mapping
            if( container == null )
            {
              container = m_panel.GetAvailableContainer( configuration, i, containerDataPath );

              listBox.PrepareContainerForItem( container, containerDataPath );
              container.Measure( viewport );

              // Update the configuration size
              if( configuration.UpdateCachedSize( container ) )
              {
                // When the cached size is updated, 
                // we need to recursively call UpdateViewportContent 
                // to ensure the size and position of each element
                // is correctly represented in the ContainerDescriptions
                needRecursiveCall = true;
              }

              this.AddDataPathToContainerMapping( containerDataPath, container );
            }
            else if( container.IsDirty )
            {
              listBox.PrepareContainerForItem( container, containerDataPath );
              container.Measure( viewport );

              // Update the configuration size
              if( configuration.UpdateCachedSize( container ) )
              {
                // When the cached size is updated, 
                // we need to recursively call UpdateViewportContent 
                // to ensure the size and position of each element
                // is correctly represented in the ContainerDescriptions
                needRecursiveCall = true;
              }
            }

            // To avoid recycling a container with the IsMouseOver state
            // while scrolling
            if( isScrollManagerActive )
              container.IsMouseOver = false;

            container.AbsoluteOpacity = containerDescription.OpacityFactor;
            container.AbsoluteScaleFactor = containerDescription.ScalingFactor;
            container.AbsoluteRotationAngle = containerDescription.RotateAngle;

            // The maximum height for a container is the viewport height
            container.MaximumHeight = viewport.Height;
            // The maximum width for a container is the viewport width
            container.MaximumWidth = viewport.Width;

            container.AbsoluteOffset = containerDescription.TargetPosition;

            if( containerDescription.ZIndex != ZIndexFunction.NoZIndex )
            {
              container.ZOrder = containerDescription.ZIndex;
            }
            else
            {
              // containerDescription.ScalingFactor is computed using the ScalingFunction
              if( scalingFunction != null )
              {
                container.ZOrder = ( int )( containerDescriptionCount * containerDescription.ScalingFactor );
              }
              else if( ( containerDataPath != null ) && ( containerDataPath.LastChild is GroupDataItem ) )
              {
                // Ensure the ZOrder is greater than the number of containers in the viewport
                // when a Group Break is detected
                container.ZOrder = m_containerDescriptions.Count * 2;
              }
              else
              {
                container.ZOrder = containerCounter;
              }
            }

            containerCounter++;

            m_panel.AdjustContainerTargetPosition( container );

            if( !m_isUpdatingViewportForTransition )
            {
              container.UpdateLayoutTransform();
              container.UpdateSize();
            }

            if( needRecursiveCall )
              break;
          }

          this.UpdateStickyHeader( viewport );

          ListBoxContainer focusedContainer = m_panel.SelectionAndFocusManager.FindFocusedContainer();

          // Layout the FocusedContainer out of view if it was not layouted
          if( ( focusedContainer != null ) && ( focusedContainer != m_stickyHeader ) )
          {
            DataPath focusedContainerDataPath = focusedContainer.DataPath;

            // If the focused container DataPath isn't is the container descriptions means the container isn't in
            // the layout. So we need to layout it out of view. We can't just recycle it, we need to have
            // a reference on it so we can bring it into view.
            if( ( focusedContainerDataPath != null ) && ( m_containerDescriptions[ focusedContainerDataPath ] == null ) )
            {
              // Only layout the focused container out of view if no animations
              // are currently running
              if( !m_isUpdatingViewportForTransition && ( m_transitionAnimations.Count == 0 ) )
              {
                focusedContainer.AbsoluteOffset = PanelBase.OutOfViewPoint;
                focusedContainer.UpdateLayoutTransform();
                focusedContainer.UpdateSize();
              }
            }
            // Do not add it to DataPathToContainerMapping since it is 
            // currently layouted out of view and it will be fetched
            // using this.Children when UpdateCachedFocusedContainer
            // will be called
          }

          // Only call UpdateViewportCompleted when
          // no recursive call is required
          if( !needRecursiveCall )
            this.OnUpdateViewportCompleted();
        }

        // This must be called OUTSIDE the DeferViewportUpdate
        // else it will have no effect
        if( needRecursiveCall )
        {
          //TransitionAnimation configurationChangeTransition = new FadingItemsSizeChangedTransitionAnimation();
          //if( !this.LaunchTransition( configurationChangeTransition, this.ViewportSizeInPixels ) )
          this.UpdateViewportContent( viewport );
        }

      }

      private void UpdateStickyHeader( Size viewport )
      {
        // Recycle the sticky header if we don't need it anymore or if
        // the group hearder changed
        bool applyPrepare = false;
        bool recycleStickyHeader = false;

        if( m_stickyHeader != null )
        {
          if( m_containerDescriptions.ForcedStickyContainerDescription == null )
          {
            recycleStickyHeader = true;
          }
          else if( !m_containerDescriptions.ForcedStickyContainerDescription.DataPath.Equals( m_stickyHeader.DataPath ) )
          {
            recycleStickyHeader = true;
          }

          if( recycleStickyHeader )
          {
            m_stickyHeader.RecycleEvenIfFocused = true;
            m_panel.AddContainerToRecyclingWaitingList( m_stickyHeader );
            m_stickyHeader = null;
          }
        }

        // If we need to display the sticky header
        if( m_containerDescriptions.ForcedStickyContainerDescription != null )
        {
          ContainerDescription stickyDescription = m_containerDescriptions.ForcedStickyContainerDescription;
          ListBoxContainerConfigurationBase stickyConfiguration = stickyDescription.Configuration;

          // Try to dequeue one container for this configuration.
          // DO NOT verify if it is already in m_dataPathToContainerMapping
          // since it is not part of the normal layout. It is forced
          // into the layout as the first item of the Viewport
          if( m_stickyHeader == null )
          {
            m_stickyHeader = m_panel.GetAvailableContainer( stickyConfiguration, 999, stickyDescription.DataPath );
            applyPrepare = true;
          }
          else if( m_stickyHeader.Configuration != stickyConfiguration )
          {
            // Make sure to prepare the container if the 
            // configuration changed
            applyPrepare = true;
          }

          if( applyPrepare )
          {
            this.ParentListBox.PrepareContainerForItem( m_stickyHeader, stickyDescription.DataPath );
            m_stickyHeader.Measure( viewport );

            // Update the configuration size
            stickyConfiguration.UpdateCachedSize( m_stickyHeader );
          }

          // The maximum height for a container is the viewport height
          m_stickyHeader.MaximumHeight = viewport.Height;
          // The maximum width for a container is the viewport width
          m_stickyHeader.MaximumWidth = viewport.Width;

          m_stickyHeader.AbsoluteOpacity = 1.0;
          m_stickyHeader.AbsoluteOffset = stickyDescription.TargetPosition;
          m_stickyHeader.AbsoluteRotationAngle = stickyDescription.RotateAngle;
          m_stickyHeader.ZOrder = ZIndexFunction.MaxZIndex;

          if( !m_isUpdatingViewportForTransition )
          {
            m_stickyHeader.UpdateLayoutTransform();
            m_stickyHeader.UpdateSize();
          }
        }
      }

      private void OnUpdateViewportCompleted()
      {
        if( !m_isUpdatingViewportForTransition )
        {
          m_panel.ProcessRecyclingWaitingList();
        }

        m_panel.ScrollManager.AverageItemSize = m_panel.GetAverageContainerSize();

      }

      #endregion

      #region DataPath to Container mapping

      private void ClearDataPathToContainerMapping()
      {
        m_dataPathToContainerMapping.Clear();
      }

      internal bool RemoveDataPathToContainerMapping( DataPath dataPath )
      {
        return m_dataPathToContainerMapping.Remove( dataPath );
      }

      internal void AddDataPathToContainerMapping( DataPath dataPath, ListBoxContainer container )
      {
        Debug.Assert( m_stickyHeader != container, "We would be adding the sticky header to the dataPathToContainerMapping." );
        m_dataPathToContainerMapping.Add( dataPath, container );
      }

      #endregion

      #region Layout Containers Methods

      private int LayoutFromCurrentToViewportEnd( Size viewport, ContainerDescriptionList containerDescriptions )
      {
        ListBox listBox = this.ParentListBox;
        DataPath currentDataPath = null;

        int numberOfLayoutedItems = 0;
        bool isEnumeratorAfterLast = false;
        double partialOffset = ( m_panel.ItemOffset % 1 );

        // Group
        bool isGroupBreak = false;

        PositioningFunctionBase positioningFunction = m_panel.PositioningFunction;

        // Keep a reference on each configuration to minimize the use of reflexion
        ListBoxContainerConfigurationBase itemConfiguration = listBox.GetContainerFactory( ConfigurationType.Item );
        ListBoxContainerConfigurationBase groupConfiguration = listBox.GetContainerFactory( ConfigurationType.GroupHeader );
        m_panel.ActualContainerSize = itemConfiguration.PrepareSize;

        Size itemCorrectedPreparedSize = m_panel.PositioningFunction.GetCorrectedContainerSize( itemConfiguration.PrepareSize, viewport );

        Size groupCorrectedPreparedSize = m_panel.PositioningFunction.GetCorrectedContainerSize( groupConfiguration.PrepareSize, viewport );

        positioningFunction.UpdateEffectiveViewportSizeInPixels( m_panel.ViewportSizeInPixels );

        // This methods Resets the positioning function internal states to prepare the next layout pass
        positioningFunction.PrepareLayoutPass( partialOffset, itemCorrectedPreparedSize, groupCorrectedPreparedSize );

        double itemContainersInViewport = 0.0d;
        double containerProportionInViewport = 0.0d;
        DataPath groupDataPath = null;

        bool isNavigatingToGroupWithAsyncData = this.GetIsNavigatingToGroupWithAsyncData();

        // If there is one, get the previous DataPath
        DataPath previousDataPath = null;
        if( m_panel.EnumeratorHelper.Move( -1 ) )
        {
          previousDataPath = m_panel.EnumeratorHelper.CurrentDataPath;
        }
        m_panel.EnumeratorHelper.Move( 1 );

        while( ( !positioningFunction.IsLayoutFilled( true ) ) && ( !isEnumeratorAfterLast ) )
        {
          currentDataPath = m_panel.EnumeratorHelper.CurrentDataPath;

          // Look for group break
          isGroupBreak = m_panel.IsDataPathAGroupBreak( previousDataPath, currentDataPath );
          isGroupBreak |= ( isNavigatingToGroupWithAsyncData && ( numberOfLayoutedItems == 0 ) && ( currentDataPath.Depth > 1 ) );

          // If we hit a group break, we generate au group header and layout it before 
          // the current data path
          if( ( isGroupBreak ) )
          {
            // Get the group DataPath from the current DataPath
            groupDataPath = currentDataPath.CreateAncestorPath( Math.Max( 1, currentDataPath.Depth - 1 ) );

            this.LayoutContainer( true, groupDataPath, containerDescriptions );
          }

          containerProportionInViewport = this.LayoutContainer( true, currentDataPath, containerDescriptions );

          itemContainersInViewport += containerProportionInViewport;

          numberOfLayoutedItems++;

          // When moving forward, current DataPath becomes previous
          previousDataPath = currentDataPath;

          // Move the enumrator forward
          if( !m_panel.EnumeratorHelper.Move( 1 ) )
            isEnumeratorAfterLast = true;

        }

        m_panel.m_itemsInViewport = itemContainersInViewport;
        return numberOfLayoutedItems;
      }

      private int LayoutFromCurrentToViewportStart( Size viewport, ContainerDescriptionList containerDescriptions )
      {
        ListBox listBox = this.ParentListBox;
        DataPath currentDataPath = null;

        int numberOfMoveBackward = 0;
        bool isEnumeratorBeforeFirst = false;
        double partialOffset = ( m_panel.ItemOffset % 1 );

        // Group
        bool isGroupBreak = false;

        PositioningFunctionBase positioningFunction = m_panel.PositioningFunction;

        // Keep a reference on each configuration to minimize the use of reflexion
        ListBoxContainerConfigurationBase itemConfiguration = listBox.GetContainerFactory( ConfigurationType.Item );
        m_panel.ActualContainerSize = itemConfiguration.PrepareSize;

        double itemContainersInViewport = 0.0d;
        double containerProportionInViewport = 0.0d;
        DataPath groupDataPath = null;

        // Move back from 1 item. The Current item has been layouted be 
        // the TopBottom layout pass.
        numberOfMoveBackward++;
        if( !m_panel.EnumeratorHelper.Move( -1 ) )
          isEnumeratorBeforeFirst = true;

        currentDataPath = m_panel.EnumeratorHelper.CurrentDataPath;

        // Move back once more to get the previous DataPath.  From here on out
        // the EnumeratorHelper's CurrentIndex and CurrentDataPath will correspond to the 
        // previous DataPath.  This is done to prevent moving back and forth in the
        // DataSource when checking for group breaks
        DataPath previousDataPath = null;
        if( m_panel.EnumeratorHelper.Move( -1 ) )
        {
          previousDataPath = m_panel.EnumeratorHelper.CurrentDataPath;
        }

        while( ( !positioningFunction.IsLayoutFilled( false ) ) && ( !isEnumeratorBeforeFirst ) )
        {
          // Look for group break
          isGroupBreak = m_panel.IsDataPathAGroupBreak( previousDataPath, currentDataPath );

          // While layout object in reverse (from bottom to top), if we hit a group break
          // we must first layout the item and the layout the group header
          containerProportionInViewport = this.LayoutContainer( false, currentDataPath, containerDescriptions );

          // Count items in viewport (to update the viewport height)
          itemContainersInViewport += containerProportionInViewport;

          // Now, layout the group header
          if( ( isGroupBreak ) )
          {
            // Get the group DataPath from the current DataPath
            groupDataPath = currentDataPath.CreateAncestorPath( Math.Max( 1, currentDataPath.Depth - 1 ) );

            this.LayoutContainer( false, groupDataPath, containerDescriptions );
          }

          numberOfMoveBackward++;

          // When moving backwards, previous DataPath becomes current
          currentDataPath = previousDataPath;

          // If new current DataPath is null, this means the old previous DataPath was
          // null, and thus that there is no more Data
          if( currentDataPath == null )
          {
            isEnumeratorBeforeFirst = true;
          }
          else
          {
            // Move the enumrator backward
            previousDataPath = null;
            if( m_panel.EnumeratorHelper.Move( -1 ) )
            {
              previousDataPath = m_panel.EnumeratorHelper.CurrentDataPath;
            }
          }

        }

        // Move one step forward to return to current DataPath instead of previous
        // Prevent this behaviour if the current was at the top, since moving backwards
        // from index -1 stays on index -1
        if( !isEnumeratorBeforeFirst )
        {
          m_panel.EnumeratorHelper.Move( 1 );
        }

        // Add the number of layouted items in the layout pass to 
        // the actual computed viewport height.
        m_panel.m_itemsInViewport += itemContainersInViewport;
        return numberOfMoveBackward;

      }

      // This methods place the container on the positioning function and
      // add the newly added container description to the container description list
      private double LayoutContainer( bool layoutForward,
        DataPath dataPathToLayout,
        ContainerDescriptionList containerDescriptions )
      {

        ContainerDescription containerDescription = new ContainerDescription();
        PositioningFunctionBase positioningFunction = m_panel.PositioningFunction;
        IOpacityFunction opacityFunction = m_panel.GetOpacityFunction();
        IScalingFunction scalingFunction = m_panel.GetScalingFunction();
        IZIndexFunction zindexFunction = m_panel.GetZIndexFunction();

        // Keep a reference on each configuration to minimize the use of reflexion
        ListBoxContainerConfigurationBase itemConfiguration = this.ParentListBox.GetContainerFactory( ConfigurationType.Item );
        ListBoxContainerConfigurationBase groupHeaderConfiguration = this.ParentListBox.GetContainerFactory( ConfigurationType.GroupHeader );

        Size viewport = m_panel.ViewportSizeInPixels;

        Point containerPosition = new Point();
        double containerProportionInViewport = 0.0d;
        int layoutProgressionPercentage = 0;
        double rotateAngle = 0.0d;

        // Get the container configuration from the current DataPath
        containerDescription.Configuration = m_panel.GetContainerConfigurationFromDataPath( dataPathToLayout,
                                                                                         itemConfiguration,
                                                                                         groupHeaderConfiguration );

        // Compute a rough estimated size for the container so the positioning can be more accurate
        m_panel.ComputeEstimatedSizeForConfig( dataPathToLayout, viewport );

        Size preparedSize = containerDescription.Configuration.PrepareSize;

        // Correct the PreparedSize to ensure its value is at most the 
        // Viewport's size
        Size correctedPreparedSize = m_panel.PositioningFunction.GetCorrectedContainerSize( preparedSize, viewport );

        // Ask the positioning function to place the container accordingly to 
        // the layouted direction
        if( layoutForward )
        {
          positioningFunction.PlaceContainerForward( correctedPreparedSize,
            ( containerDescription.Configuration.GetType() == typeof( ListBoxGroupHeaderConfiguration ) ),
            out containerPosition,
            out containerProportionInViewport,
            out layoutProgressionPercentage,
            out rotateAngle );
        }
        else
        {
          positioningFunction.PlaceContainerBackward( correctedPreparedSize,
            ( containerDescription.Configuration.GetType() == typeof( ListBoxGroupHeaderConfiguration ) ),
            out containerPosition,
            out containerProportionInViewport,
            out layoutProgressionPercentage,
            out rotateAngle );
        }

        // Assigned the computed values to the container description
        containerDescription.DataPath = dataPathToLayout;
        containerDescription.TargetPosition = containerPosition;
        containerDescription.RotateAngle = rotateAngle;
        containerDescription.ProportionInEffectiveViewport = containerProportionInViewport;

        containerDescription.OpacityFactor = ( opacityFunction != null )
          ? opacityFunction.GetOpacityFactor( layoutProgressionPercentage, false, dataPathToLayout.LastChild is GroupDataItem )
          : 1;

        containerDescription.ScalingFactor = ( scalingFunction != null )
          ? scalingFunction.GetScalingFactor( layoutProgressionPercentage, false, dataPathToLayout.LastChild is GroupDataItem )
          : 1;

        containerDescription.ZIndex = ( zindexFunction != null )
          ? zindexFunction.GetZIndex( layoutProgressionPercentage, false, dataPathToLayout.LastChild is GroupDataItem )
          : ZIndexFunction.NoZIndex;

        // Add the container description to the container description list
        if( layoutForward )
        {
          containerDescriptions.Add( containerDescription );
        }
        else
        {
          containerDescriptions.Insert( 0, containerDescription );
        }

        // Return the proportion of the container visible in the effective viewport.
        return containerProportionInViewport;
      }

      private bool GetIsNavigatingToGroupWithAsyncData()
      {
        if( m_panel.ParentListBox.IsAsyncDataSource )
        {
          foreach( TransitionAnimation transition in m_transitionAnimations )
          {
            if( ( transition is ScrollingDownTransitionAnimation ) || ( transition is ScrollingUpTransitionAnimation ) || ( transition is LoadingTransitionAnimation ) )
            {
              return true;
            }
          }
        }

        return false;
      }

      #endregion

      #region Transitions Methods

      internal void LaunchViewChangedTransition()
      {
        TransitionAnimation viewChangedTransition = new ViewChangingTransitionAnimation( this.ParentListBox.AnimationSettings );
        viewChangedTransition.Completed += this.OnTransitionCompleted;

        if( !this.LaunchTransition( viewChangedTransition, m_panel.ViewportSizeInPixels, null ) )
        {
          this.UpdateViewportContent( m_panel.ViewportSizeInPixels );
        }
      }

      internal void LaunchLoadingTransition( Size viewport )
      {
        if( !this.LaunchTransition( new LoadingTransitionAnimation( this.ParentListBox.AnimationSettings ), viewport, null ) )
        {
          this.UpdateViewportContent( viewport );
        }
      }

      internal bool LaunchTransition( TransitionAnimation transition, Size viewport, object state )
      {
        if( transition == null )
          return false;

        transition.Completed += this.OnTransitionCompleted;

        m_transitionAnimations.Add( transition );

        // Initialize the transition with the actual viewport content
        transition.Initialize( this.GetLayoutedContainers( true ), state );

        // Make sure the configuration has been applied
        m_panel.Refresh();

        // Update the viewport and make sure no recycling happens
        m_isUpdatingViewportForTransition = true;

        this.UpdateViewportContent( viewport );

        m_isUpdatingViewportForTransition = false;

        // Start the transition with the resulting viewport content
        transition.StartTransitionAnimation( this.GetLayoutedContainers( true ) );

        return true;
      }

      private void OnTransitionCompleted( object sender, TransitionAnimationEventArgs e )
      {
        TransitionAnimation transition = sender as TransitionAnimation;

        if( transition == null )
          throw new ListBoxInternalException();

        transition.Completed -= this.OnTransitionCompleted;

        m_transitionAnimations.Remove( transition );

        //This will go through all the elements marked for removal in the transitions queue 
        //and "remove" those which do not have any ongoing transition animations.
        m_panel.ProcessRecyclingWaitingList();
      }

      internal void AddNewContainerToCurrentTransitions( ListBoxContainer container )
      {
        foreach( TransitionAnimation transition in m_transitionAnimations )
        {
          transition.AddContainer( container );
        }
      }

      internal bool GetHasRunningTransitionAnimations()
      {
        return m_transitionAnimations.Count > 0;
      }

      #endregion

      #region BringIntoView Methods

      internal bool BringContainerCompletlyIntoView( ListBoxContainer targetContainer, NavigationInfo navigationInfo )
      {
        // If we can't get the container description neither the positioning function
        // we can't find the position of the next container (we don't know anything about the layout).
        if( ( m_containerDescriptions == null ) ||
            ( m_panel.PositioningFunction == null ) ||
            ( !m_panel.PositioningFunction.IsScrollOffsetDifferenceBetweenPositionsSupported ) )
          return true;

        BringContainerCompletelyIntoViewNavigationAction navigationAction = new BringContainerCompletelyIntoViewNavigationAction( targetContainer.DataPath, m_panel, navigationInfo );
        double targetIndex = navigationAction.CalculateTargetScrollIndex();

        if( LayoutUtils.AreClose( targetIndex, m_panel.ScrollManager.TargetIndex ) )
          return false;

        // Scroll to the target.
        m_panel.StartScrollingAnimation( targetIndex, navigationInfo.IsPageScrolling, navigationAction );

        return true;
      }

      internal bool IsContainerOutOfViewport( ListBoxContainer container )
      {
        return this.IsContainerOutOfViewport( container, false );
      }

      internal bool IsContainerOutOfViewport( ListBoxContainer container, bool allowPartiallyVisible )
      {
        if( !m_panel.IsContainerOutOfViewportSupported )
          return false;

        if( container == null )
          return true;

        Rect panelRect = new Rect( new Point(), m_panel.RenderSize );

        // Correct the visibility rectangle so container under the sticky header are not marked as visible.
        // If no sticky header, the visibility rectangle remains unchanged.
        Size stickyHeaderSize = new Size( 0.0d, 0.0d );

        if( m_containerDescriptions != null )
        {
          if( m_containerDescriptions.ForcedStickyContainerDescription != null )
          {
            stickyHeaderSize = new Size( m_stickyHeader.ActualElementWidth, m_stickyHeader.ActualElementHeight );
          }
        }

        panelRect = m_panel.PositioningFunction.GetCorrectedViewportVisibilityRect( stickyHeaderSize, panelRect );

        Rect containerRect = new Rect( new Point(), container.RenderSize );
        Rect targetContainerToPanelRect = container.TransformToVisual( m_panel ).TransformBounds( containerRect );

        panelRect.Intersect( targetContainerToPanelRect );

        // Container is partially visible if intersection is not empty, completely visible if intersection
        // is less than a pixel away from the container size, due to pixel size approximation errors.
        bool isPartiallyVisible = ( panelRect.Height > 0d ) && ( panelRect.Width > 0d );
        bool isCompletelyVisible = ( ( containerRect.Height - panelRect.Height ) < 1 ) && ( ( containerRect.Width - panelRect.Width ) < 1 );

        if( allowPartiallyVisible && isPartiallyVisible )
          return false;

        // If partial visibility is not allowed and confirmed, a container that is
        // not completely into view is out of viewport.
        return !isCompletelyVisible;
      }

      internal DataPath GetNextOrPreviousDataPathForCurrentDataPath( DataPath currentPath, NavigationInfo navigationInfo )
      {
        int currentPathIndex = m_containerDescriptions.IndexOf( currentPath );

        if( currentPathIndex == -1 )
          return null;

        ListBoxContainer nextOrPreviousSelectableContainer = null;

        do
        {
          if( navigationInfo.IsForwardScrolling )
          {
            // We need to move in container description to get the next selectable container

            while( ( nextOrPreviousSelectableContainer == null ) && ( currentPathIndex < m_containerDescriptions.Count - 1 ) )
            {
              ContainerDescription nextContainerDescription = m_containerDescriptions[ currentPathIndex + 1 ];

              nextOrPreviousSelectableContainer = this.GetContainerFromPath( nextContainerDescription.DataPath, true );
              currentPathIndex++;
            }
          }
          else
          {
            while( ( nextOrPreviousSelectableContainer == null ) && ( currentPathIndex >= 1 ) )
            {
              ContainerDescription previousContainerDescription = m_containerDescriptions[ currentPathIndex - 1 ];

              nextOrPreviousSelectableContainer = this.GetContainerFromPath( previousContainerDescription.DataPath, true );
              currentPathIndex--;
            }
          }

          // Ensure to get the next container with a different DataPath
        } while( ( nextOrPreviousSelectableContainer != null )
                 && ( nextOrPreviousSelectableContainer.DataPath != null )
                 && ( nextOrPreviousSelectableContainer.DataPath.Equals( currentPath ) ) );

        return ( nextOrPreviousSelectableContainer != null )
              ? nextOrPreviousSelectableContainer.DataPath
              : null;
      }

      // Must find the first or last fully visible and return it, else null to force a scroll to an index +/- viewport away
      internal DataPath GetFirstOrLastFullyVisibleDataPathForCurrentDataPath(
        DataPath currentPath,
        NavigationInfo navigationInfo )
      {
        double scrollManagerTargetIndex = m_panel.ScrollManager.TargetIndex;
        int containerDescriptionCount = m_containerDescriptions.Count;
        double viewport = m_panel.ItemsInViewport;
        double maxTargetIndex = this.ParentListBox.DataSourceEnumeratorWrapper.ItemCount - viewport;

        //If we are already on the last full page and doing page scrolling
        if( navigationInfo.IsForwardScrolling && ( scrollManagerTargetIndex >= maxTargetIndex ) )
        {
          //return the last element.
          if( containerDescriptionCount > 0 )
          {
            return m_containerDescriptions[ containerDescriptionCount - 1 ].DataPath;
          }
          else
          {
            return null;
          }
        }
        //If we are already on the first full page and doing page scrolling
        else if( !navigationInfo.IsForwardScrolling && ( scrollManagerTargetIndex == 0 ) )
        {
          //return the first element.
          if( containerDescriptionCount > 0 )
          {
            return m_containerDescriptions[ 0 ].DataPath;
          }
          else
          {
            return null;
          }
        }
        else
        {
          if( navigationInfo.IsForwardScrolling )
          {
            // We want the last fully visible DataPath in the viewport or null
            // if the currentDataPath is already the last fully visible
            DataPath lastFullyVisibleDataPath = this.GetLastFullyVisibleDataPath();

            if( ( lastFullyVisibleDataPath != null ) && ( !lastFullyVisibleDataPath.Equals( currentPath ) ) )
            {
              return lastFullyVisibleDataPath;
            }
            else
            {
              return null;
            }
          }
          else
          {
            // We want the first fully visible DataPath in the viewport or null
            // if the currentDataPath is already the first fully visible
            DataPath firstFullyVisibleDataPath = this.GetFirstFullyVisibleDataPath();

            if( ( firstFullyVisibleDataPath != null ) && ( !firstFullyVisibleDataPath.Equals( currentPath ) ) )
            {
              return firstFullyVisibleDataPath;
            }
            else
            {
              return null;
            }
          }
        }
      }

      #endregion

      #region Internal Methods

      internal IDisposable DeferUpdateViewport()
      {
        return new DeferLayoutUpdateDisposable( this );
      }

      internal void Reset()
      {
        if( m_stickyHeader != null )
        {
          // Even though we do not explicitly want to defer container recycling
          // the container will be put in the waiting list if it is not elligible
          // for recycling.
          m_stickyHeader.RecycleEvenIfFocused = true;
          m_panel.RecycleContainerAndLayoutOutOfView( m_stickyHeader );
          m_stickyHeader = null;
        }

        this.ClearDataPathToContainerMapping();
      }

      internal DataPath GetFirstFullyVisibleDataPath()
      {
        int count = m_containerDescriptions.Count;

        if( count == 0 )
          return null;

        int index = 0;

        while( index < count )
        {
          DataPath dataPath = m_containerDescriptions[ index ].DataPath;

          ListBoxContainer container = this.GetContainerFromPath( dataPath, true );

          // null container will be received for group headers
          if( container != null )
          {
            if( !this.IsContainerOutOfViewport( container ) )
            {
              return dataPath;
            }
          }

          index++;
        }

        return null;
      }

      internal DataPath GetLastFullyVisibleDataPath()
      {
        int count = m_containerDescriptions.Count;

        if( count == 0 )
          return null;

        while( count > 0 )
        {
          DataPath dataPath = m_containerDescriptions[ count - 1 ].DataPath;

          ListBoxContainer container = this.GetContainerFromPath( dataPath, true );

          if( !this.IsContainerOutOfViewport( container ) )
            return dataPath;

          count--;
        }

        return null;
      }

      internal IEnumerable<ListBoxContainer> GetLayoutedContainers( bool includeStickyHeader )
      {
        List<ListBoxContainer> layoutedContainers = new List<ListBoxContainer>();
        foreach( ListBoxContainer container in m_dataPathToContainerMapping.Values )
        {
          layoutedContainers.Add( container );
        }

        if( ( m_stickyHeader != null ) && ( includeStickyHeader ) )
        {
          layoutedContainers.Add( m_stickyHeader );
        }

        return layoutedContainers;
      }

      internal Size GetStickyHeaderSize()
      {
        if( ( m_panel.AllowStickyHeaders ) && ( m_containerDescriptions.ForcedStickyContainerDescription != null ) )
        {
          return new Size( m_stickyHeader.AbsoluteElementWidth, m_stickyHeader.AbsoluteElementHeight );
        }

        return new Size( 0.0d, 0.0d );
      }

      internal ListBoxContainer GetContainerFromPath( DataPath dataPath )
      {
        return this.GetContainerFromPath( dataPath, false );
      }

      internal ListBoxContainer GetContainerFromPath( DataPath dataPath, bool selectableOnly )
      {
        if( dataPath == null )
          return null;

        // Use m_dataPathToContainerMapping to return only layouted containers
        foreach( ListBoxContainer container in m_dataPathToContainerMapping.Values )
        {
          if( selectableOnly && !container.IsSelectable )
            continue;

          DataPath containerDataPath = container.DataPath;

          if( containerDataPath == null )
            continue;

          if( containerDataPath.Equals( dataPath ) )
            return container;
        }



        // Check if the DataPath is the focused container's DataPath
        // because the focused container isn't mapped in the dataPathToContainermapping
        ListBoxContainer focusedContainer = m_panel.SelectionAndFocusManager.FindFocusedContainer();

        if( focusedContainer != null )
        {
          if( focusedContainer.DataPath.Equals( dataPath ) )
          {
            return focusedContainer;
          }
        }

        return null;
      }

      #endregion

      #region Private Field

      private PanelBase m_panel;

      private int m_inhibitLayoutCount;
      private bool m_isUpdatingViewportForTransition;

      private List<TransitionAnimation> m_transitionAnimations = new List<TransitionAnimation>();

      private ContainerDescriptionList m_containerDescriptions;
      private Dictionary<DataPath, ListBoxContainer> m_dataPathToContainerMapping = new Dictionary<DataPath, ListBoxContainer>();
      private ListBoxContainer m_stickyHeader;

      #endregion


      #region DeferLayoutUpdateDisposable Private Class

      private class DeferLayoutUpdateDisposable : IDisposable
      {
        public DeferLayoutUpdateDisposable( PanelLayoutManager layoutManager )
        {
          if( layoutManager == null )
            throw new ArgumentNullException( "layoutManager" );

          m_layoutManager = layoutManager;
          m_layoutManager.m_inhibitLayoutCount++;
        }

        public void Dispose()
        {
          if( m_layoutManager == null )
            return;

          m_layoutManager.m_inhibitLayoutCount--;

          if( m_layoutManager.m_inhibitLayoutCount == 0 )
          {
            if( m_layoutManager.m_panel.MeasureOverrideInhibited )
            {
              m_layoutManager.m_panel.InvalidateMeasure();
            }
            else if( m_layoutManager.m_panel.ArrangeOverrideInhibited )
            {
              m_layoutManager.m_panel.InvalidateArrange();
            }

            m_layoutManager.m_panel.MeasureOverrideInhibited = false;
            m_layoutManager.m_panel.ArrangeOverrideInhibited = false;
          }

          m_layoutManager = null;
        }

        private PanelLayoutManager m_layoutManager;
      }

      #endregion
    }
  }
}
